package com.pearlsoft.rms.vcenter;

import java.util.ArrayList;
import java.util.List;

import javax.xml.ws.soap.SOAPFaultException;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Service;

import com.pearlsoft.rms.VappConfig;
import com.pearlsoft.rms.VappConfig.VmConfig;
import com.vmware.vim25.ConfigTarget;
import com.vmware.vim25.DynamicProperty;
import com.vmware.vim25.LocalizedMethodFault;
import com.vmware.vim25.ManagedObjectReference;
import com.vmware.vim25.NetworkSummary;
import com.vmware.vim25.ObjectContent;
import com.vmware.vim25.ObjectSpec;
import com.vmware.vim25.ObjectUpdate;
import com.vmware.vim25.ObjectUpdateKind;
import com.vmware.vim25.PropertyChange;
import com.vmware.vim25.PropertyChangeOp;
import com.vmware.vim25.PropertyFilterSpec;
import com.vmware.vim25.PropertyFilterUpdate;
import com.vmware.vim25.PropertySpec;
import com.vmware.vim25.TaskInfo;
import com.vmware.vim25.TaskInfoState;
import com.vmware.vim25.UpdateSet;
import com.vmware.vim25.VimPortType;
import com.vmware.vim25.VirtualDevice;
import com.vmware.vim25.VirtualDeviceConfigSpec;
import com.vmware.vim25.VirtualDeviceConfigSpecFileOperation;
import com.vmware.vim25.VirtualDeviceConfigSpecOperation;
import com.vmware.vim25.VirtualDisk;
import com.vmware.vim25.VirtualDiskFlatVer2BackingInfo;
import com.vmware.vim25.VirtualEthernetCard;
import com.vmware.vim25.VirtualEthernetCardNetworkBackingInfo;
import com.vmware.vim25.VirtualMachineConfigInfo;
import com.vmware.vim25.VirtualMachineConfigSpec;
import com.vmware.vim25.VirtualMachineNetworkInfo;
import com.vmware.vim25.VirtualMachineRuntimeInfo;
import com.vmware.vim25.VirtualPCNet32;
import com.vmware.vim25.VirtualSCSIController;

/**
 * 
 * @author rainshow
 * @Created 2012-5-14 上午10:19:33
 */

@Service("vappReconfig")
public class VappReconfig extends VcenterClient {
    private ManagedObjectReference virtualMachine = null;
    private String storeName;
    private String diskMode;

    public VappReconfig() {
    }

    private void reconfigMemory(VirtualMachineConfigSpec vmConfigSpec, int value) throws Exception {
        try {
            vmConfigSpec.setMemoryMB(new Long(value));
        } catch (SOAPFaultException sfe) {
            printSoapFaultException(sfe);
        } catch (java.lang.NumberFormatException nfe) {
            System.out.println("Value of Memory update must " + "be either Custom or Integer");
            return;
        }
    }

    private void reconfigCpu(VirtualMachineConfigSpec vmConfigSpec, int value) throws Exception {
        try {
            vmConfigSpec.setNumCPUs(value);
        } catch (SOAPFaultException sfe) {
            printSoapFaultException(sfe);
        } catch (java.lang.NumberFormatException nfe) {
            System.out.println("Value of CPU update must " + "be either Custom or Integer");
            return;
        }
    }

    private void reconfigDisk(VirtualMachineConfigSpec vmConfigSpec, VmConfig config) throws Exception {
        VirtualDeviceConfigSpec vdiskSpec = getDiskDeviceConfigSpec(config);
        if (vdiskSpec != null) {
            List<VirtualDeviceConfigSpec> vdiskSpecArray = new ArrayList<VirtualDeviceConfigSpec>();
            vdiskSpecArray.add(vdiskSpec);
            vmConfigSpec.getDeviceChange().addAll(vdiskSpecArray);
        } else {
            return;
        }
    }

    @SuppressWarnings("unused")
    private void reconfigNic(VirtualMachineConfigSpec vmConfigSpec, VmConfig config) throws Exception {
        List<VirtualDeviceConfigSpec> nicSpec = getNICDeviceConfigSpec(config);
        if (nicSpec != null) {
            List<VirtualDeviceConfigSpec> nicSpecArray = new ArrayList<VirtualDeviceConfigSpec>();
            nicSpecArray.addAll(nicSpec);
            vmConfigSpec.getDeviceChange().addAll(nicSpecArray);
        } else {
            return;
        }
    }

    public void reConfig(VappConfig config) throws Exception {
        List<VmConfig> vmConfig = config.getVmConfig();
        for (VmConfig vmconf : vmConfig) {
            virtualMachine = this.getVmByVMname(vmconf.getFullName());
            VirtualMachineConfigSpec vmConfigSpec = new VirtualMachineConfigSpec();
            if (vmconf.getMemSize() > 0) {
                this.reconfigMemory(vmConfigSpec, vmconf.getMemSize());
            }
            if (vmconf.getCpuCount() > 0) {
                this.reconfigCpu(vmConfigSpec, vmconf.getCpuCount());
            }
            if (vmconf.getDiskSize() > 0) {
                this.reconfigDisk(vmConfigSpec, vmconf);
            }
            //this.reconfigNic(vmConfigSpec, vmconf);
            ManagedObjectReference tmor = this.getConnection().getVimPort()
                    .reconfigVMTask(virtualMachine, vmConfigSpec);
            monitorTask(tmor);
        }
    }

    private VirtualDeviceConfigSpec getDiskDeviceConfigSpec(VmConfig config) throws Exception {
        VirtualDeviceConfigSpec diskSpec = new VirtualDeviceConfigSpec();
        List<DynamicProperty> configInfoList = getDynamicProarray(virtualMachine, "VirtualMachine", "config");
        VirtualMachineConfigInfo vmConfigInfo = (VirtualMachineConfigInfo) configInfoList.get(0).getVal();

        VirtualDisk disk = new VirtualDisk();
        VirtualDiskFlatVer2BackingInfo diskfileBacking = new VirtualDiskFlatVer2BackingInfo();
        String dsName = storeName;

        int ckey = 0;
        int unitNumber = -1;
        List<VirtualDevice> test = vmConfigInfo.getHardware().getDevice();
        for (int k = 0; k < test.size(); k++) {
            VirtualDevice vd = test.get(k);
            if (vd instanceof VirtualDisk) { //挂接硬盘，一个IDE controller可以挂2个
                ckey = vd.getControllerKey();
                if (vd.getUnitNumber() > unitNumber) {
                    unitNumber = vd.getUnitNumber();
                }
            } else if (vd instanceof VirtualSCSIController) { //SCSI方式的挂接
                ckey = vd.getKey();
            }
        }
        if (unitNumber == -1) {
            unitNumber = test.size();
        }
        unitNumber += 1;
        String fileName = "[" + dsName + "] " + config.getFullName() + "/" + config.getFullName() + "_" + unitNumber
                + ".vmdk";
        diskfileBacking.setFileName(fileName);
        diskfileBacking.setDiskMode(diskMode);
        disk.setControllerKey(ckey);
        disk.setUnitNumber(unitNumber);
        disk.setBacking(diskfileBacking);
        int size = 1024 * (int) config.getDiskSize();
        disk.setCapacityInKB(size);
        disk.setKey(-1);
        diskSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD);
        diskSpec.setFileOperation(VirtualDeviceConfigSpecFileOperation.CREATE);
        diskSpec.setDevice(disk);
        return diskSpec;
    }

    private String waitForTask(ManagedObjectReference taskmor) throws Exception {
        List<String> infoList = new ArrayList<String>();
        infoList.add("info.state");
        infoList.add("info.error");
        List<String> stateList = new ArrayList<String>();
        stateList.add("state");
        Object[] result = waitForValues(taskmor, infoList, stateList, new Object[][] { new Object[] {
                TaskInfoState.SUCCESS, TaskInfoState.ERROR } });
        if (result[0].equals(TaskInfoState.SUCCESS)) {
            return "sucess";
        } else {
            List<DynamicProperty> tinfoProps = new ArrayList<DynamicProperty>();
            tinfoProps = getDynamicProarray(taskmor, "Task", "info");
            TaskInfo tinfo = (TaskInfo) tinfoProps.get(0).getVal();
            LocalizedMethodFault fault = tinfo.getError();
            String error = "Error Occured";
            if (fault != null) {
                error = fault.getLocalizedMessage();
                System.out.println("Message " + fault.getLocalizedMessage());
            }
            return error;
        }
    }

    private void monitorTask(ManagedObjectReference tmor) throws Exception {
        if (tmor != null) {
            String result = waitForTask(tmor);
            if (result.equalsIgnoreCase("sucess")) {
                System.out.println("Task Completed Sucessfully");
            } else {
                System.out.println("Failure " + result);
            }
        }
    }

    /**
     * Handle Updates for a single object. waits till expected values of
     * properties to check are reached Destroys the ObjectFilter when done.
     * @param objmor MOR of the Object to wait for
     * @param filterProps Properties list to filter
     * @param endWaitProps Properties list to check for expected values these be
     *        properties of a property in the filter properties list
     * @param expectedVals values for properties to end the wait
     * @return true indicating expected values were met, and false otherwise
     */
    private Object[] waitForValues(ManagedObjectReference objmor, List<String> filterProps, List<String> endWaitProps,
            Object[][] expectedVals) throws Exception {
        // version string is initially null
        String version = "";
        Object[] endVals = new Object[endWaitProps.size()];
        Object[] filterVals = new Object[filterProps.size()];
        ObjectSpec objSpec = new ObjectSpec();
        objSpec.setObj(objmor);
        objSpec.setSkip(Boolean.FALSE);
        PropertyFilterSpec spec = new PropertyFilterSpec();
        spec.getObjectSet().add(objSpec);
        PropertySpec propSpec = new PropertySpec();
        propSpec.getPathSet().addAll(filterProps);
        propSpec.setType(objmor.getType());
        spec.getPropSet().add(propSpec);
        VimPortType vimPort = this.getConnection().getVimPort();
        ManagedObjectReference filterSpecRef = vimPort.createFilter(getConnection().getPropCollector(), spec, true);

        boolean reached = false;

        UpdateSet updateset = null;
        List<PropertyFilterUpdate> filtupary = null;
        PropertyFilterUpdate filtup = null;
        List<ObjectUpdate> objupary = null;
        ObjectUpdate objup = null;
        List<PropertyChange> propchgary = null;
        PropertyChange propchg = null;
        while (!reached) {
            boolean retry = true;
            while (retry) {
                try {
                    updateset = vimPort.waitForUpdates(getConnection().getPropCollector(), version);
                    retry = false;
                } catch (SOAPFaultException sfe) {
                    printSoapFaultException(sfe);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (updateset != null) {
                version = updateset.getVersion();
            }
            if (updateset == null || updateset.getFilterSet() == null) {
                continue;
            }

            // Make this code more general purpose when PropCol changes later.
            filtupary = updateset.getFilterSet();
            filtup = null;
            for (int fi = 0; fi < filtupary.size(); fi++) {
                filtup = filtupary.get(fi);
                objupary = filtup.getObjectSet();
                objup = null;
                propchgary = null;
                for (int oi = 0; oi < objupary.size(); oi++) {
                    objup = objupary.get(oi);

                    if (objup.getKind() == ObjectUpdateKind.MODIFY || objup.getKind() == ObjectUpdateKind.ENTER
                            || objup.getKind() == ObjectUpdateKind.LEAVE) {
                        propchgary = objup.getChangeSet();
                        for (int ci = 0; ci < propchgary.size(); ci++) {
                            propchg = propchgary.get(ci);
                            updateValues(endWaitProps, endVals, propchg);
                            updateValues(filterProps, filterVals, propchg);
                        }
                    }
                }
            }

            Object expctdval = null;
            // Check if the expected values have been reached and exit the loop if done.
            // Also exit the WaitForUpdates loop if this is the case.
            for (int chgi = 0; chgi < endVals.length && !reached; chgi++) {
                for (int vali = 0; vali < expectedVals[chgi].length && !reached; vali++) {
                    expctdval = expectedVals[chgi][vali];
                    reached = expctdval.equals(endVals[chgi]) || reached;
                }
            }
        }

        // Destroy the filter when we are done.
        vimPort.destroyPropertyFilter(filterSpecRef);

        return filterVals;
    }

    private void updateValues(List<String> props, Object[] vals, PropertyChange propchg) {
        for (int findi = 0; findi < props.size(); findi++) {
            if (propchg.getName().lastIndexOf(props.get(findi)) >= 0) {
                if (propchg.getOp() == PropertyChangeOp.REMOVE) {
                    vals[findi] = "";
                } else {
                    vals[findi] = propchg.getVal();
                }
            }
        }
    }

    private List<VirtualDeviceConfigSpec> getNICDeviceConfigSpec(VmConfig config) throws Exception {
        List<VirtualDeviceConfigSpec> specs = new ArrayList<VirtualDeviceConfigSpec>();
        int nicCount = config.getNetworkCount();
        String networkName = getNetworkName("VM Network");
        if (networkName == null) {
            return specs;
        }
        for (int i = 0; i < nicCount; i++) {
            VirtualDeviceConfigSpec nicSpec = new VirtualDeviceConfigSpec();
            nicSpec.setOperation(VirtualDeviceConfigSpecOperation.ADD);
            VirtualEthernetCard nic = new VirtualPCNet32();
            VirtualEthernetCardNetworkBackingInfo nicBacking = new VirtualEthernetCardNetworkBackingInfo();
            nicBacking.setDeviceName(networkName);
            nic.setAddressType("generated");
            nic.setBacking(nicBacking);
            nic.setKey(4);
            nicSpec.setDevice(nic);
            specs.add(nicSpec);
        }
        return specs;
    }

    private String getNetworkName(String name) throws Exception {
        String networkName = null;
        List<DynamicProperty> runtimeInfoList = getDynamicProarray(virtualMachine, "VirtualMachine", "runtime");
        VirtualMachineRuntimeInfo vmRuntimeInfo = (VirtualMachineRuntimeInfo) runtimeInfoList.get(0).getVal();
        List<DynamicProperty> envBrowserList = getDynamicProarray(virtualMachine, "VirtualMachine",
                "environmentBrowser");
        ManagedObjectReference envBrowser = (ManagedObjectReference) envBrowserList.get(0).getVal();
        ManagedObjectReference hmor = vmRuntimeInfo.getHost();

        if (hmor != null) {
            ConfigTarget configTarget = this.getConnection().getVimPort().queryConfigTarget(envBrowser, null);
            if (configTarget.getNetwork() != null) {
                for (int i = 0; i < configTarget.getNetwork().size(); i++) {
                    VirtualMachineNetworkInfo netInfo = configTarget.getNetwork().get(i);
                    NetworkSummary netSummary = netInfo.getNetwork();
                    if (netSummary.isAccessible()) {
                        if (netSummary.getName().equalsIgnoreCase(name)) {
                            networkName = netSummary.getName();
                            break;
                        }
                    }
                }
                if (networkName == null) {
                    System.out.println("Specify the Correct Network Name");
                    return null;
                }
            }
            return networkName;
        } else {
            System.out.println("No Host is responsible to run this VM");
            return null;
        }
    }

    private List<DynamicProperty> getDynamicProarray(ManagedObjectReference ref, String type, String propertyString)
            throws Exception {
        PropertySpec propertySpec = new PropertySpec();
        propertySpec.setAll(Boolean.FALSE);
        propertySpec.getPathSet().add(propertyString);
        propertySpec.setType(type);

        // Now create Object Spec
        ObjectSpec objectSpec = new ObjectSpec();
        objectSpec.setObj(ref);
        objectSpec.setSkip(Boolean.FALSE);
        objectSpec.getSelectSet().addAll(buildFullTraversal());
        // Create PropertyFilterSpec using the PropertySpec and ObjectPec
        // created above.
        PropertyFilterSpec propertyFilterSpec = new PropertyFilterSpec();
        propertyFilterSpec.getPropSet().add(propertySpec);
        propertyFilterSpec.getObjectSet().add(objectSpec);
        List<PropertyFilterSpec> listPfs = new ArrayList<PropertyFilterSpec>(1);
        listPfs.add(propertyFilterSpec);
        List<ObjectContent> oContList = retrievePropertiesAllObjects(listPfs);
        ObjectContent contentObj = oContList.get(0);
        List<DynamicProperty> objList = contentObj.getPropSet();
        return objList;
    }

    public void setStoreName(String storeName) {
        this.storeName = storeName;
    }

    public void setDiskMode(String diskMode) {
        this.diskMode = diskMode;
    }

    public static void main(String[] args) throws Throwable {
        ApplicationContext context = new ClassPathXmlApplicationContext(
                new String[] { "classpath:applicationContext.xml" });
        VappReconfig reconfig = context.getBean("vappReconfig", VappReconfig.class);
        VappConfig config = new VappConfig("app1");
        config.addVm("app1-host-1", "XP_TPL", 1, 1, 2, 1024 * 1024, 1);
        reconfig.reConfig(config);
    }

}
